package com.chenfan.ccp.plug.mybatis.util;

import com.chenfan.ccp.entity.BaseEntity;
import com.chenfan.ccp.plug.mybatis.annotation.Column;
import com.chenfan.ccp.plug.mybatis.annotation.JavaDocRead;
import com.chenfan.ccp.plug.mybatis.annotation.Table;
import com.chenfan.ccp.plug.mybatis.annotation.Id;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.Builder;
import lombok.Data;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


/**
 * 自动生成代码块
 */
public class AutoGenerateCode {


    private static String getShortFieldName(Field field, Boolean isShortFlag, Map<String, Field> fieldMap) {

        if (!isShortFlag) {
            return field.getName();
        }
        char[] chars = field.getName().toCharArray();
        int len = chars.length;

        StringBuilder charBuild = new StringBuilder();
        for (int i = len - 1; i >= 0; i--) {
            if (Character.isUpperCase(chars[i])) {

                charBuild.append(Character.toLowerCase(chars[i]));


                if (fieldMap.containsKey(charBuild.reverse().toString())) {

                    charBuild.reverse();
                    charBuild.delete(charBuild.length() - 1, charBuild.length());
                    charBuild.append(chars[i]);
                } else {

                    charBuild.reverse();
                    break;
                }


            } else {
                charBuild.append(chars[i]);
            }

        }

        charBuild.reverse();

        if (fieldMap.putIfAbsent(charBuild.toString(), field) == null) {
            return charBuild.toString();
        } else {
            return "";
        }


    }

    public static void generate(Class... classes) {
        generateClass(classes, null);

    }

    public static void generate(FileType fileTypes, Class... classes) {
        generateClass(classes, new ArrayList() {
            {
                add(fileTypes);
            }
        });

    }


    private static void generateClass(Class[] classes, List<FileType> fileTypes) {
        for (Class aClass : classes) {

            Class tmpClass = aClass.getSuperclass();
            boolean isExistSUperFlag = false;
            while (tmpClass != Object.class) {
                if (tmpClass == BaseEntity.class) {
                    isExistSUperFlag = true;
                    break;
                }
                tmpClass = tmpClass.getSuperclass();
            }
            if (!isExistSUperFlag) {
                System.err.println(String.format("use auto code class=%s must extend =%s", aClass.getSimpleName(),
                        BaseEntity.class.getCanonicalName()));
                continue;
            }

            autoGenerate(aClass, fileTypes);
        }
    }


    /**
     * 获取当前源文件路径
     *
     * @return
     */
    private static String getCurrentSourcePath() {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        String currentClassName = stackTrace[stackTrace.length - 1].getClassName();
        String fileName = currentClassName.replaceAll("\\.",
                File.separator.equalsIgnoreCase("\\") ? "\\\\" : File.separator) + ".java";
        String absoluteFiles = getAbsoluteFiles(fileName,
                Paths.get(".").toFile());


        return absoluteFiles.substring(0, absoluteFiles.lastIndexOf(fileName));
    }


    /**
     * 根据路径获取对应的文件的绝对路径
     *
     * @param fileName 文件的地址
     * @param pathFile 文件的根目录
     * @return
     */
    private static String getAbsoluteFiles(String fileName, File pathFile) {
        File[] files = pathFile.listFiles();
        String resultPath = "";
        for (File file : files) {
            if (!resultPath.isEmpty()) {
                break;
            }
            if (file.isDirectory()) {
                resultPath = getAbsoluteFiles(fileName, file);
            } else if (file.getName().endsWith(".java") && file.getAbsolutePath().endsWith(fileName)) {
                resultPath = file.getAbsolutePath();
                break;
            }
        }

        return resultPath;

    }

    protected static void autoGenerate(Class aClass, List<FileType> fileTypes) {


        String path = getCurrentSourcePath();


        Map<String, String> map = null;
        try {
            map = getAllFields(aClass);

            if (map.isEmpty()) {
                System.err.println(String.format("class=%s must use  annotation =%s", aClass.getSimpleName(),
                        Table.class.getCanonicalName()));
                return;
            }

        } catch (NoSuchFieldException e) {

        }

        //保存生成文件类型的映射
        Map<String, String> generateFileTypeNameMap = new HashMap<>();

        if (fileTypes != null && !fileTypes.isEmpty()) {
            for (FileType fileType : fileTypes) {
                generateFileTypeNameMap.put(fileType.getFileName(), fileType.getFileName());
            }
        }


        String className = map.get("class_name");


        Configuration configuration = new Configuration(Configuration.VERSION_2_3_20);
        //生成文件的存放路径
        File saveFile;
        try {
            //指定模板存放的路径
            configuration.setClassForTemplateLoading(AutoGenerateCode.class, "/template");


            Map<String, String> names = new HashMap<>();
            names.put("controller.ftl", className + "Controller.java");
            names.put("dao.ftl", className + "Dao.java");
            names.put("daoImpl.ftl", className + "DaoImpl.java");
            names.put("service.ftl", className + "Service.java");
            names.put("serviceImpl.ftl", className + "ServiceImpl.java");
            names.put("vo.ftl", className + "Vo.java");
            String parentPath = map.get("package_name").replaceAll("\\.", "/");
            Map<String, String> paths = new HashMap<>();
            paths.put("controller.ftl", parentPath + "/controller");
            paths.put("dao.ftl", parentPath + "/dao");
            paths.put("daoImpl.ftl", parentPath + "/dao/impl/");
            paths.put("service.ftl", parentPath + "/service/");
            paths.put("serviceImpl.ftl", parentPath + "/service/impl/");
            paths.put("vo.ftl", parentPath + "/vo");
            for (Map.Entry<String, String> entry : names.entrySet()) {

                if (!generateFileTypeNameMap.isEmpty() && generateFileTypeNameMap.get(entry.getKey()) == null) {
                    continue;
                }

                File parentFile = new File(path + paths.get(entry.getKey()));
                if (!parentFile.exists()) {
                    parentFile.mkdirs();
                }
                saveFile = new File(path + paths.get(entry.getKey())
                        , entry.getValue());
                Template template = configuration.getTemplate(entry.getKey());

                Writer tempWriter = new BufferedWriter(
                        new OutputStreamWriter(
                                new FileOutputStream(saveFile)));
                try {
                    template.process(map, tempWriter);
                } catch (TemplateException e) {
                    e.printStackTrace();
                }
                tempWriter.flush();
                tempWriter.close();

                JavaForm.format(saveFile.toPath());

            }


            //生成Po中的拷贝方法
            String poClassPath = path + "/" + parentPath + "/" + map.get("class_package");
            String poClassName = aClass.getSimpleName();
            Path poFile = Paths.get(poClassPath + "/" + poClassName + ".java");
            String importVoPath = "import " + map.get("package_name") + ".vo." + map.get("class_name") + "Vo;\n";
            String importKeyWord = "copy" + map.get("class_name") + "Vo()";
            String newLine = "    public " + map.get("class_name") + "Vo  " + importKeyWord + " {\n  " + map.get("class_name") + "Vo  vo=new " +
                    map.get("class_name") + "Vo(); \n  org.springframework.beans.BeanUtils.copyProperties(this, vo); \n   return vo;\n     }\n ";

            createNewCopyMethodPo(poFile, importVoPath, importKeyWord, newLine);

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 生成po中新的拷贝方法
     *
     * @param file
     * @throws IOException
     */
    private static void createNewCopyMethodPo(Path file, String importVoPath, String keyWord, String newLine) throws IOException {

        List<String> originalLines = Files.readAllLines(file);

        String allLine = originalLines.stream().collect(Collectors.joining("\n"));
        //判断是否存在
        if (allLine.contains(keyWord)) {
            return;
        }

        //判断导包是否存在
        if (!allLine.contains(importVoPath)) {
            originalLines.set(0, originalLines.get(0) + "\n\n" + importVoPath);
        }

        int len = originalLines.size();
        int index = 0;

        for (int i = len - 1; i > 0; i--) {
            if (originalLines.get(i).contains("}")) {
                index = i;
                break;
            }

        }
        if (index != 0) {
            originalLines.set(index, newLine);
            if (index == len - 1) {
                originalLines.add("}");
            } else {
                originalLines.set(index + 1, "}");
            }

        }

        Files.write(file, originalLines, StandardOpenOption.WRITE);


    }

    /**
     * 根据类获取所有属性
     *
     * @param aClass
     * @return
     */
    private static Map<String, String> getAllFields(Class<?> aClass) throws NoSuchFieldException {

        Map<String, String> paramMap = new HashMap<>();
        Class<?> rootClass = aClass;

        Table table = rootClass.getAnnotation(Table.class);


        if (table == null) {

            return paramMap;

        }
        paramMap.put("entity_name", table.commit());
        String totalPackageName = rootClass.getPackage().getName();
        paramMap.put("package_name", totalPackageName.substring(0, totalPackageName.lastIndexOf(".")));
        paramMap.put("class_package", totalPackageName.substring(totalPackageName.lastIndexOf(".") + 1));
        String className = rootClass.getSimpleName().split("Po")[0];
        paramMap.put("class_name", className);
        String lowerClassName = className.substring(0, 1).toLowerCase() +
                className.substring(1);
        paramMap.put("lower_class_name", lowerClassName);


        paramMap.put("base_url", lowerClassName);


        StringBuilder paramCommentBuild = new StringBuilder();
        StringBuilder paramQueryBuild = new StringBuilder();
        StringBuilder paramImportBuild = new StringBuilder();
        StringBuilder paramBuild = new StringBuilder();
        StringBuilder fieldBuild = new StringBuilder();

        Map<String, Field> fieldMap = new HashMap<>();
        //关键字保留
        fieldMap.put("time", Integer.class.getDeclaredField("MIN_VALUE"));
        fieldMap.put("createTime", Integer.class.getDeclaredField("MIN_VALUE"));
        fieldMap.put("updateTime", Integer.class.getDeclaredField("MIN_VALUE"));
        fieldMap.put("flag", Integer.class.getDeclaredField("MIN_VALUE"));
        fieldMap.put("deletedFlag", Integer.class.getDeclaredField("MIN_VALUE"));


        Field[] fieldArr = aClass.getDeclaredFields();


        Map<String, Field> fieldThree = new TreeMap(
                (Comparator<String>) (s1, s2) -> {
                    if (s1.length() > s2.length()) {
                        return 1;
                    } else if (s1.length() < s2.length()) {
                        return -1;
                    } else {
                        return s1.compareTo(s2);
                    }
                }
        );

        for (Field field : fieldArr) {

            if (Modifier.isFinal(field.getModifiers())) {
                continue;
            }

            fieldThree.put(field.getName(), field);


        }


        while (!aClass.getCanonicalName().equals(Object.class.getCanonicalName())) {
            aClass = aClass.getSuperclass();
            fieldArr = aClass.getDeclaredFields();
            for (Field field : fieldArr) {

                if (Modifier.isProtected(field.getModifiers()) || Modifier.isProtected(field.getModifiers())
                        && !Modifier.isFinal(field.getModifiers())) {
                    fieldThree.put(field.getName(), field);

                }
            }
        }
        Boolean isShortFlag = rootClass.getAnnotation(Data.class) != null
                ? rootClass.getAnnotation(Data.class).matchSuffix() : Boolean.TRUE;

        for (Field field : fieldThree.values()) {
            if (!getFieldBuild(table.commit(), field, isShortFlag, fieldMap, paramCommentBuild,
                    paramQueryBuild, paramBuild, className, lowerClassName, fieldBuild, paramImportBuild)) {
                continue;
            }
        }


        paramMap.put("entity_name", table.commit());
        paramMap.put("param_comment", paramCommentBuild.toString());
        paramMap.put("param", paramBuild.toString());
        paramMap.put("class_field", fieldBuild.toString());
        paramMap.put("param_condition", paramQueryBuild.toString());
        paramMap.put("import_package", paramImportBuild.toString());

        paramMap.put("new_class_instance", rootClass.getAnnotation(Builder.class) != null ?
                className + "Po.builder().build()" : "new " + className + "Po()");
        return paramMap;
    }

    /**
     * 获取Field build
     *
     * @param entityName
     * @param field
     * @param paramCommentBuild
     * @param paramQueryBuild
     * @param fieldMap
     * @param paramBuild
     * @param className
     * @param lowerClassName
     * @param fieldBuild
     * @return
     */
    private static boolean getFieldBuild(String entityName, Field field,
                                         Boolean isShortFlag,
                                         Map<String, Field> fieldMap,
                                         StringBuilder paramCommentBuild,
                                         StringBuilder paramQueryBuild,
                                         StringBuilder paramBuild,
                                         String className,
                                         String lowerClassName,
                                         StringBuilder fieldBuild, StringBuilder paramImportBuild) {

        String fieldName = getShortFieldName(field, isShortFlag, fieldMap);
        if (fieldName.isEmpty() || fieldName.equals("createTime")
                || fieldName.equals("updateTime") || fieldName.equals("deletedFlag")) {
            return false;
        }
        Type genericType = field.getGenericType();
        String fieldNamePrefix = field.getType().getSimpleName();
        StringBuilder javaDocReadBuild = new StringBuilder();

        if (genericType != null
                && genericType instanceof ParameterizedType) {

            ParameterizedType pt = (ParameterizedType) genericType;

            Type[] types = pt.getActualTypeArguments();


            if (types != null && types.length > 0) {
                List classes = new ArrayList();
                for (int i = 0; i < types.length; i++) {
                    classes.add(types[i].getTypeName());
                }
                if (!classes.isEmpty()) {
                    String genericName = classes.toString().substring(1, classes.toString().length() - 1);
                    fieldNamePrefix += "<" + genericName + ">";
                    javaDocReadBuild.append("@");
                    javaDocReadBuild.append(JavaDocRead.class.getCanonicalName());
                    javaDocReadBuild.append("({");
                    javaDocReadBuild.append(genericName.replaceAll(",", ".class,") + ".class");
                    javaDocReadBuild.append("})\n");


                }
            }


        }


        String filedParam = fieldNamePrefix + " " + fieldName;

        Column column = field.getAnnotation(Column.class);
        String commit = String.format("%s的Id,修改或者查询的需要", entityName);
        if (column == null) {
            Id id = field.getAnnotation(Id.class);

            if (id == null) {
                return false;
            }

        } else {
            commit = column.commit();
            if (commit.isEmpty()) {
                commit = "空白";
            } else {
                Pattern javaDocPattern = Pattern.compile("(.*)\\{@(link|linkplain)(.*)}");
                Matcher commitMatcher = javaDocPattern.matcher(commit);
                if (!commitMatcher.find()) {
                    commit = commit.replaceAll("\\s", "");
                }

            }
        }
        //参数说明
        if (paramCommentBuild.length() > 1) {
            paramCommentBuild.append("\n");
        }
        paramCommentBuild.append("* @param ");
        paramCommentBuild.append(fieldName);
        paramCommentBuild.append("        ");
        paramCommentBuild.append(commit);

        //入参
        if (paramBuild.length() > 1) {
            paramBuild.append(",");
        }
        paramBuild.append("@RequestParam(required = false) ");
        paramBuild.append(filedParam);

        //成员变量
        if (fieldBuild.length() > 1) {
            fieldBuild.append("\n");
        }
        fieldBuild.append("/***");
        fieldBuild.append(commit);
        fieldBuild.append("**/\n");
        //增加javaDoc的读
        if (javaDocReadBuild.length() > 1) {
            fieldBuild.append(javaDocReadBuild.toString());
            javaDocReadBuild.delete(0, javaDocReadBuild.length());
        }
        fieldBuild.append("private ");
        fieldBuild.append(filedParam);
        fieldBuild.append(";");

        //导入包的管理 排出不需要导入和基本类型
        if ((!field.getType().getCanonicalName().startsWith("java.lang")) &&
                field.getType().getCanonicalName().contains(".")) {
            //保证只加载一个
            if (!paramImportBuild.toString().contains(field.getType()
                    .getCanonicalName())) {
                paramImportBuild.append("import ");
                paramImportBuild.append(field.getType().getCanonicalName());
                paramImportBuild.append("; ");
            }
        }


        //查询变量
        String higherFieldName = field.getName().substring(0, 1).toUpperCase() +
                field.getName().substring(1);

        if (field.getAnnotation(Id.class) != null) {
            return true;

        } else if (field.getType().isPrimitive()) {
            paramQueryBuild.append("\n build.eq(");

        } else if (field.getType() == String.class) {
            paramQueryBuild.append("if (StringUtils.isNotEmpty(");
            paramQueryBuild.append(lowerClassName);
            paramQueryBuild.append("Po.get");
            paramQueryBuild.append(higherFieldName);
            paramQueryBuild.append("())) { \n build.like(");

        } else {
            paramQueryBuild.append("if (");
            paramQueryBuild.append(lowerClassName);
            paramQueryBuild.append("Po.get");
            paramQueryBuild.append(higherFieldName);
            paramQueryBuild.append("()!=null ) { \n build.eq(");

        }


        String getMethodPrefix = field.getType().getCanonicalName().equals("boolean") ? "is" : "get";

        paramQueryBuild.append(className);
        paramQueryBuild.append("Po::");
        paramQueryBuild.append(getMethodPrefix);
        paramQueryBuild.append(higherFieldName);
        paramQueryBuild.append(",");
        paramQueryBuild.append(lowerClassName);
        paramQueryBuild.append("Po.");
        paramQueryBuild.append(getMethodPrefix);
        paramQueryBuild.append(higherFieldName);
        paramQueryBuild.append("()); \n");
        if (!field.getType().isPrimitive()) {
            paramQueryBuild.append("}\n");
        }

        return true;

    }


    /**
     * 生成类型
     */
    public enum FileType {
        CONTROLLER("controller.ftl"),
        DAO("dao.ftl"),
        DAO_IMPL("daoImpl.ftl"),
        SERVICE("service.ftl"),
        SERVICE_IMPL("serviceImpl.ftl"),
        VO("vo.ftl");
        private final String fileName;

        FileType(String fileName) {
            this.fileName = fileName;
        }

        public String getFileName() {
            return fileName;
        }
    }

    private static class JavaForm {


        protected static Map<String, String> mapZY = new HashMap<>();

        public static void format(Path file) throws IOException {
            StringBuilder content = new StringBuilder();
            Files.readAllLines(file)
                    .stream().forEach(x -> {
                content.append(x);
                if (x.trim().startsWith("@")) {
                    content.append("%n");
                }


            });

            Files.write(file, formJava(content.toString()).getBytes(), StandardOpenOption.WRITE);

        }

        /**
         * 格式化java代码
         *
         * @param data
         * @return
         */
        private static String formJava(String data) {
            String dataTmp = replaceStrToUUid(data, "\"");
            dataTmp = replaceStrToUUid(dataTmp, "'");
            dataTmp = repalceHHF(dataTmp, "\n", "");
            dataTmp = repalceHHF(dataTmp, "{", "{\n");
            dataTmp = repalceHHF(dataTmp, "}", "}\n");
            dataTmp = repalceHHF(dataTmp, "/**", "\n/**\n");
            dataTmp = repalceHHF(dataTmp, "/*[^*]", "\n/*\n");
            dataTmp = repalceHHF(dataTmp, "* @", "\n* @");
            dataTmp = repalceHHF(dataTmp, "@[A-Z]", "\n@");
            dataTmp = repalceHHF(dataTmp, "**/", "\n**/\n");
            dataTmp = repalceHHF(dataTmp, "*/", "\n*/\n");
            dataTmp = repalceHHF(dataTmp, ";", ";\n");
            dataTmp = repalceHHF(dataTmp, "//", "\n//");
            dataTmp = repalceHHFX(dataTmp, "\n");
            for (Map.Entry<String, String> r : mapZY.entrySet()) {
                dataTmp = dataTmp.replace(r.getKey(), r.getValue());
            }
            if (dataTmp == null) {
                return String.format(data).replaceAll("p4j", "    ");
            }

            return String.format(dataTmp).replaceAll("p4j", "    ");
        }

        /**
         * 循环替换指定字符为随机uuid  并将uui存入全局map:mapZY
         *
         * @param string 字符串
         * @param type   指定字符
         **/
        private static String replaceStrToUUid(String string, String type) {
            Matcher slashMatcher = Pattern.compile(type).matcher(string);
            boolean bool = false;
            StringBuilder sb = new StringBuilder();
            int indexHome = -1; //开始截取下标
            while (slashMatcher.find()) {
                int indexEnd = slashMatcher.start();
                String tmp = string.substring(indexHome + 1, indexEnd); //获取"号前面的数据
                if (indexHome == -1 || bool == false) {
                    sb.append(tmp);
                    bool = true;
                    indexHome = indexEnd;
                } else {
                    if (bool) {
                        String tem2 = "";
                        for (int i = indexEnd - 1; i > -1; i--) {
                            char c = string.charAt(i);
                            if (c == '\\') {
                                tem2 += c;
                            } else {
                                break;
                            }
                        }
                        int tem2Len = tem2.length();
                        if (tem2Len > -1) {
                            //结束符前有斜杠转义符 需要判断转义个数奇偶   奇数是转义了  偶数才算是结束符号
                            if (tem2Len % 2 == 1) {
                                //奇数 非结束符
                            } else {
                                //偶数才算是结束符号
                                String uuid = UUID.randomUUID().toString().replace("-", "").toLowerCase();
                                uuid = type + uuid + type;
                                mapZY.put(uuid, type + tmp + type);
                                sb.append(uuid);
                                bool = false;
                                indexHome = indexEnd;
                            }
                        }
                    }
                }
            }
            sb.append(string.substring(indexHome + 1));
            return sb.toString();
        }


        //处理换行
        private static String repalceHHF(String data, String a, String b) {
            try {

                if (a.equals("{") && data.contains("{@")) {
                    String replaceText = String.valueOf(System.nanoTime());
                    data = data.replaceAll("\\{@", replaceText).
                            replace(a, "$<<yunwangA>>$<<yunwangB>>")
                            .replaceAll(replaceText, "\\{@");
                } else {
                    data = data.replace(a, "$<<yunwangA>>$<<yunwangB>>");
                }

                String[] arr = data.split("$<<yunwangA>>");
                StringBuilder result = new StringBuilder();
                if (arr != null) {
                    for (int i = 0; i < arr.length; i++) {
                        String t = arr[i];
                        result.append(t.trim());
                        if (t.indexOf("//") != -1 && "\n".equals(a)) {
                            result.append("\n");
                        }
                    }
                }
                String res = result.toString();
                res = res.replace("$<<yunwangB>>", b);
                res = res.replace("$<<yunwangA>>", "");
                return res;
            } catch (Exception e) {
            }
            return null;
        }

        //处理缩进
        private static String repalceHHFX(String data, String a) {
            try {
                String[] arr = data.split(a);
                StringBuilder result = new StringBuilder();
                if (arr != null) {
                    String zbf = "    ";
                    Stack<String> stack = new Stack<String>();
                    for (int i = 0; i < arr.length; i++) {
                        String tem = arr[i].trim();
                        if (tem.indexOf("{") != -1) {
                            String kg = getStack(stack, false);
                            if (kg == null) {
                                result.append((tem + "\n"));
                                kg = "";
                            } else {
                                kg = kg + zbf;
                                result.append(kg + tem + "\n");
                            }
                            stack.push(kg);
                        } else if (tem.indexOf("}") != -1) {
                            String kg = getStack(stack, true);
                            if (kg == null) {
                                result.append(tem + "\n");
                            } else {
                                result.append(kg + tem + "\n");
                            }
                        } else {
                            String kg = getStack(stack, false);
                            if (kg == null) {
                                result.append(tem + "\n");
                            } else {
                                result.append(kg + zbf + tem + "\n");
                            }
                        }
                    }
                }
                String res = result.toString();
                return res;
            } catch (Exception e) {
            }
            return null;
        }


        private static String getStack(Stack<String> stack, boolean bool) {
            String result = null;
            try {
                if (bool) {
                    return stack.pop();
                }
                return stack.peek();
            } catch (EmptyStackException e) {
            }
            return result;
        }

    }


}
